package com.spring_mvc.spring_mvc.v1.servlet;



import com.spring_mvc.spring_mvc.v1.annotation.*;
import lombok.Data;
import lombok.SneakyThrows;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author PABLO
 * @Date 2022/5/4 13:49
 * @Desc 继承HttpServlet实现servlet规范
 * 项目启动入口
 */
public class DispatchServlet extends HttpServlet {

    //根据contextConfigLocation获得properties文件路径
    /*
    * <init-param>
			<!--指定配置文件名字-->
			<param-name>contextConfigLocation</param-name>
			<param-value>application.properties</param-value>
		</init-param>
    * */
    private final static String LOCATION = "contextConfigLocation";
    //配置文件key
    private final static String propertiesKey = "scanPackage";
    //根据key获取配置文件内容
    /*
     * scanPackage=com.bj.summary.spring_mvc.controller,com.bj.summary.spring_mvc.service
     * */
    private Properties properties = new Properties();

    //存放扫描后得到的class文件名称
    private List<String> classNames = new ArrayList<String>();

    //IOC容器
    private Map<String, Object> ioc = new HashMap<String, Object>();

    //保存所有的Url和方法的映射关系
    private List<Handler> handlerMapping = new ArrayList<Handler>();

    /**
     * @Description: 初始化操作
     * @Author: PABLO
     * @Date: 2022/5/4 13:50
     * @Params: [config]
     * @Return: void
     **/
    @Override
    public void init(ServletConfig config) throws ServletException {
        //初始化步骤

        //加载配置文件properties
        doLoadConfig(config.getInitParameter(LOCATION));
        //扫描配置文件properties中定义的所有类
        doScannerConfig((String) properties.get(propertiesKey));
        //初始化所有的类，并实例化，保存IOC容器中
        doInstanceAndSaveIocContainer();
        //依赖注入，初始化对象
        doInitializationInstance();
        //构造handlerMapping
        doBuildHandlerMapping();
    }

    private void doBuildHandlerMapping() {
        if (Objects.isNull(ioc) || ioc.size() == 0) return;

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            //只针对处理器
            if (!clazz.isAnnotationPresent(PABLO_Controller.class)) {
                continue;
            }

            String url = "";
            //获取Controller的url配置
            if (clazz.isAnnotationPresent(PABLO_RequestMapping.class)) {
                PABLO_RequestMapping requestMapping = clazz.getAnnotation(PABLO_RequestMapping.class);
                url = requestMapping.value();
            }

            //获取Method的url配置
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {

                //没有加RequestMapping注解的直接忽略
                if (!method.isAnnotationPresent(PABLO_RequestMapping.class)) {
                    continue;
                }

                //映射URL controller路径+方法对应路径
                PABLO_RequestMapping requestMapping = method.getAnnotation(PABLO_RequestMapping.class);
                ///pablo/add  类注解路径+某方法路径
                String regex = ("/" + url + requestMapping.value()).replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(regex);
                handlerMapping.add(new Handler(pattern, entry.getValue(), method));
                //System.out.println("mapping URL路径" + regex + ",对应方法" + method);
                //System.out.println(handlerMapping);
            }
        }
    }

    private void doInitializationInstance() {
        if (Objects.isNull(ioc) || ioc.size() == 0) return;

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //拿到实例对象中的所有属性(任何修飾符)
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            //遍历该类中的所有字段
            for (Field item : fields
            ) {
                //过滤不需要自动装配的属性
                if (!item.isAnnotationPresent(PABLO_Autowired.class)) {
                    continue;
                }
                //获取所有需要装配的字段
                PABLO_Autowired autowired = item.getAnnotation(PABLO_Autowired.class);
                String beanName = autowired.value().trim();
                //未设置对象名
                if ("".equals(beanName)) {
                    //获取默认类名
                    beanName = item.getType().getName();
                }
                item.setAccessible(true); //设置私有属性的访问权限
                try {
                    //entry.getValue()是【实例化后的某个类对象】 如testController
                    //item是该类中的属性
                    //ioc.get(beanName)，beanName是key，得到该类型对应的【具体实例化对象】
                    //给某个类型的某个属性真实的赋值，建立实例化后对象的 组合关系，这是符号引用-->直接引用
                    item.set(entry.getValue(), ioc.get(beanName));
                } catch (Exception e) {
                    e.printStackTrace();
                    continue;
                }
            }
        }
    }

    private void doInstanceAndSaveIocContainer() {
        if (Objects.isNull(classNames) || classNames.size() == 0) return;
        try {
            //获取每个类上注解
            for (String item : classNames
            ) {
                //根据完全限定名加载到内存
                Class<?> clazz = Class.forName(item);
                //根据类注解类型进行不同操作
                if (clazz.isAnnotationPresent((PABLO_Controller.class))) {
                    //默认将类首字母小写作为beanName
                    String beanName = lowerFirst(clazz.getSimpleName());
                    //key  testController小写首字母类名
                    //value  实例化后的对象
                    ioc.put(beanName, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(PABLO_Service.class)) {
                    PABLO_Service service = clazz.getAnnotation(PABLO_Service.class);
                    String beanName = service.value();
                    //用户自定义对象名优先
                    if (!"".equals(beanName.trim())) {
                        //走到这里 一定是某个类上有service注解，并且为自定义名称
                        ioc.put(beanName, clazz.newInstance());
                        continue;
                    }

                    //如果自己没设，就按接口类型创建一个实例
                    //key 接口
                    //value 接口实现类实例对象
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> i : interfaces) {
                        ioc.put(i.getName(), clazz.newInstance());
                    }

                } else {
                    //可扩展其他类注解如@Respository....
                    //类上没注解的略过
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void doScannerConfig(String packageNames) {
        //包可能有多个，数组处理
        String[] packageNameArr = packageNames.split(",");
        for (String item : packageNameArr
        ) {
            //处理每个包，将包路径转为文件路径  .-->/
            Class<? extends DispatchServlet> clazz = this.getClass();
            ClassLoader classLoader = clazz.getClassLoader();
            String packageName = "/" + item.replaceAll("\\.", "/");
            URL url =  classLoader.getResource(packageName);
            File dir = new File(url.getFile());
            for (File file : dir.listFiles()) {
                //如果是文件夹，继续递归
                if (file.isDirectory()) {
                    doScannerConfig(item + "." + file.getName());
                } else {
                    //E:\IDEALocation\giant-gator\target\classes\com\bj\summary\spring_mvc\controller\TestController.class
                    classNames.add(item + "." + file.getName().replace(".class", "").trim());
                }
            }
        }

    }

    private void doLoadConfig(String location) {
        InputStream fis = null;
        try {
            fis = this.getClass().getClassLoader().getResourceAsStream(location);
            //读取配置文件
            properties.load(fis);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != fis) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private String lowerFirst(String str) {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //get调用post
        this.doPost(req, resp);
    }


    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp); //开始始匹配到对应的方方法

        } catch (Exception e) {
            //如果匹配过程出现异常，将异常信息打印出去
            resp.getWriter().write("500 Exception,Details:\r\n" + Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]", "").replaceAll(",\\s", "\r\n"));
        }
    }

    @SneakyThrows
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {

        try { //通过请求获取对应handler，在构建handler时就已经指定 某个请求->某个方法（某些参数）
            Handler handler = getHandler(req);

            if (handler == null) {
                //如果没有匹配上，返回404错误
                resp.getWriter().write("404 Not Found");
                return;
            }


            //获取方法的参数列表 都是类型的完全限定名如java.lang.Integer java.lang.String...
            Class<?>[] paramTypes = handler.method.getParameterTypes();

            //保存所有需要自动赋值的参数值
            Object[] paramValues = new Object[paramTypes.length];

            //得到方法参数名称和对应的值  key=a参数名称  value=[具体入参值]
            Map<String, String[]> params = req.getParameterMap();
            //将实际入参值填充！！！
            for (Map.Entry<String, String[]> param : params.entrySet()) {
                //参数带中括号，去中括号
                String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
                //通过参数名称找参数下标，如果找到匹配的对象，则开始填充参数值
                if (!handler.paramIndexMapping.containsKey(param.getKey())) {
                    continue;
                }
                int index = handler.paramIndexMapping.get(param.getKey());
                paramValues[index] = convert(paramTypes[index], value);
            }


            //设置方法中的request和response对象
            int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex] = req;
            int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[respIndex] = resp;

            handler.method.invoke(handler.controller, paramValues);

        } catch (InvocationTargetException e) {
            e.getTargetException().printStackTrace();
        }
    }

    //可采用策略，如map
    private Object convert(Class<?> paramType, String value) {
        if (Integer.class == paramType) {
            return Integer.valueOf(value);
        }
        return value;
    }

    private Handler getHandler(HttpServletRequest req) throws Exception {
        if (handlerMapping.isEmpty()) {
            return null;
        }

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath, "").replaceAll("/+", "/");
        //根据请求路径匹配mapping
        for (Handler handler : handlerMapping) {
            try {
                Matcher matcher = handler.pattern.matcher(url);
                //如果没有匹配上继续下一个匹配
                if (!matcher.matches()) {
                    continue;
                }

                return handler;
            } catch (Exception e) {
                throw e;
            }
        }
        return null;
    }

    /**
     * @Author PABLO
     * @Date 2022/5/4 13:49
     * @Desc Handler记录Controller中的RequestMapping和Method的对应关系
     * 这里是在集合中绑定一对一关系，method一定只属于一个controller，一个controller可有N个method
     */
    @Data
    private class Handler {

        protected Object controller;    //保存方法对应的实例
        protected Method method;        //保存映射的方法
        protected Pattern pattern;     //正则表达式的编译表达形式  如将某个URI编译 /pablo/add
        // key是注解上的value，value是该参数的下标，记录某个value的参数值，对应的下标
        //如add(@PABLO_RequestParam("a") Integer a, @PABLO_RequestParam("b") Integer b)
        //key=a  value=0
        //key=b  value=1
        protected Map<String, Integer> paramIndexMapping;

        /**
         * 构造一个Handler基本的参数
         *
         * @param controller
         * @param method
         */
        protected Handler(Pattern pattern, Object controller, Method method) {
            this.controller = controller;
            this.method = method;
            this.pattern = pattern;

            paramIndexMapping = new HashMap<String, Integer>();
            putParamIndexMapping(method);
        }
        //将某个方法和该方法上的参数绑定，具体入参在前，req和resp在后
        private void putParamIndexMapping(Method method) {

            //提取方法中加了注解的参数
            //一个参数可能有多个注解
            Annotation[][] pa = method.getParameterAnnotations();
            for (int i = 0; i < pa.length; i++) {
                for (Annotation a : pa[i]) {
                    if (a instanceof PABLO_RequestParam) {
                        String paramName = ((PABLO_RequestParam) a).value();
                        //这里只有该注解中的value值不为null才行
                        if (!"".equals(paramName.trim())) {
                            //只有标记了该注解的参数才会被识别
                            paramIndexMapping.put(paramName, i);
                        }
                    }
                }
            }

            //提取方法中的request和response参数
            Class<?>[] paramsTypes = method.getParameterTypes();
            for (int i = 0; i < paramsTypes.length; i++) {
                Class<?> type = paramsTypes[i];
                if (type == HttpServletRequest.class ||
                        type == HttpServletResponse.class) {
                    paramIndexMapping.put(type.getName(), i);
                }
            }
        }
    }


}
